<?php
use frontend\modules\yii2\widgets\layouts\markdown\Markdown;

$this->title = 'FileTarget详解-Log-Components组件';
$this->registerMetaTag([
    'name' => 'keywords',
    'content' => 'yii,yii2,框架,应用组件,Components,使用,教程,log,fileTarget'
]);
$this->registerMetaTag([
    'name' => 'description',
    'content' => 'FileTarget可以将日志记录到本地磁盘，这也是我们最常用的日志记录方式。'
]);


$html = [
    "概述" => <<<HTML
# 将日志记录到磁盘文件
`yii\log\FileTarget`可以将日志记录到本地磁盘，这也是我们最常用的日志记录方式。
## 配置举例 id=config
常见的配置如下：
```php
[
    'components' => [
        'log' => [
            'flushInterval' => 1,
            'traceLevel' => 0,
            'targets' => [
                // 可以指定key，便于访问
                'login' => [
                    'logVars' => [],
                    'class' => 'yii\log\FileTarget',
                    'categories' => ['login'],
                    'logFile' => '@frontend/runtime/logs/login/'.date("Y-m-d").'.log',
                    'maxFileSize' => 1024 * 100, //100M
                    'maxLogFiles' => 100,
                    'exportInterval' => 1,
                    'prefix' => function() {   //日志格式自定义 回调方法
                        return "";
                    },
                ],
                // 也可以不指定key
                [
                    'logVars' => [],
                    'class' => 'yii\log\FileTarget',
                    'categories' => ['delete_user'],
                    'logFile' => '@frontend/runtime/logs/user/delete-'.date("Y-m-d").'.log',
                    'maxFileSize' => 1024 * 100, //100M
                    'maxLogFiles' => 100,
                    'exportInterval' => 1,
                    'prefix' => function() {
                        return "";
                    },
                ]
            ],
        ],
    ],
]
```
HTML,
    "logFile" => <<<HTML
## 日志文件路径（logFile） id=log-file
您可以通过`logFile`这个属性来指定当前`target`的日志需要记录到哪个日志文件中。如果没有设置，会使用`@runtime/logs/app.log`这个日志文件，为了便于日志管理，我们一般会在`app.log`同目录下创建文件夹进行日志分类管理，如：
```php
// 把删除用户的日志记录到单独的文件中
'logFile' => '@runtime/logs/user/delete.log',
```

### 一日一文件 id=daily  
如果日志量比较大，可以给日志文件名加入日志，这样每天都会产生一个新的日志文件：
```php
'logFile' => '@runtime/logs/user/delete-'.date("Y-m-d").'.log',
```

### 清理日志 id=clear
一天一个日志文件的好处是可以写shell脚本定期清理过期的日志，比如说：
```shell
// 查找创建时间10天前的日志，并删除
find runtime/logs/ -mtime +10 -type f -name \*.log | xargs rm -f

// 查找目录下所有日志文件，并删除
find runtime/logs/ -type f -name \*.log\* | xargs rm -f
```
上述在查找所有日志文件的时候使用了`\*.log\*`，这是因为达到单文件容量上限后，会在文件名后面加上数字后缀。


HTML,
    "maxFileSize" => <<<HTML
## 最大文件容量 id=maxFileSize
通过`maxFileSize`这个属性可以配置单个日志文件的最大容量，默认是`10M`，但是您应该以`kilo-bytes`为单位进行设置，比如说您允许让单个日志文件的容量达到100M，则可以参考如下配置：
```php
'maxFileSize' => 1024 * 100, //100M
```
当一个日志文件的容量达到最大值后，会将文件名修改为`.1`，因此在日积月累后，您的日志文件可能是这样的：
```shell
login.log  login.log.1  login.log.2
```

HTML,
    "maxLogFiles" => <<<HTML
## 最大文件数量 id=maxLogFiles
Yii的日志系统对文件数量也是有限制的，默认最多产生5个文件，当然你可以手动调整：
```php
// 设置最多100个文件
'maxLogFiles' => 100,
```
虽然增加了日志数量，但是保不齐还是会超出这个限制，比较有效的策略是给[日志文件加上日期](#daily)，这就意味着每天产生的日志文件数量不得超过`maxLogFiles`，再通过定期[清理过期日志文件](#clear)来释放磁盘空间，可以满足大多数的业务场景，此时您的日志大概是这样的：
```shell
2021-12-26.log    2021-12-27.log    2021-12-28.log    2021-12-29.log    2021-12-30.log
2021-12-26.log.1  2021-12-27.log.1  2021-12-28.log.1  2021-12-29.log.1  2021-12-30.log.1
2021-12-26.log.2  2021-12-27.log.2  2021-12-28.log.2  2021-12-29.log.2
```

HTML,
    "rotate" => <<<HTML
## 文件轮换 id=rotate
当日志文件大小达到`maxFileSize`时，会触发文件轮换机制，简单来说就是把已经存满的文件名添加数字后缀，轮换机制如下：
1. `app.log`达到`maxFileSize`默认的10M；
2. 将`app.log`复制为`app.log.1`，复制期间会阻塞进程；
3. 复制结束后，任然将新的日志记录到`app.log`中；
4. `app.log`第二次存满10M；
5. 复制`app.log.1`为`app.log.2`；
6. 复制`app.log`为`app.log.1`，此时原来的`app.log.1`会被覆盖；
7. 复制结束后，任然将新的日志记录到`app.log`中；
8. 如果日志数量未达到`maxLogFiles`，则重复第4步；
9. 日志文件数量达到`maxLogFiles`设置的数量（默认5个）；
10. 将`app.log.5`删除，然后复制`app.log.4`为`app.log.5`，复制`app.log.3`为`app.log.4`...；
11. 重复第3步；

> 注意：通过上述轮换机制可以知道，在轮换的时候数据的总拷贝量是`maxFileSize * maxLogFiles`级的，而且是进程阻塞式拷贝，这就意味着如果你用下面的配置，一但触发轮换，将拷贝10G的文件后才能释放阻塞的进程：
```php
'maxFileSize' => 1024 * 100, //100M
'maxLogFiles' => 100,
```

### 重命名文件 id=rename
由于`LogTarget`的日志轮换机制默认是采用拷贝的方式，此时可能会触发长时间进程阻塞，因此Yii提供了一个属性将拷贝动作替换为重命名：
```php
'rotateByCopy' => false
```
我们都知道，重命名一个文件的代价是很小的，远小于拷贝一个大文件，因此不会引起长时间的进程阻塞。
HTML,
    "" => <<<HTML

HTML,
];

// Yii::info("使能测试", "login");
// var_dump(Yii::$app->log->targets["login"]->messages = ["test", "info", "login"]);die;
// die;

echo Markdown::widget(["content" => implode("\n", $html)]);

